<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="application-name" content="OSHMI-Open Substation HMI">
<meta name="description" content="Alarm Box">
<meta name="author" content="Ricardo L. Olsen">
<meta name="viewport" content="width=850, user-scalable=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="images/tabular.png" />
<title>Visor Tabular</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />    
<style>
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(lib/SourceSansPro-Regular.woff2) format('woff2');
}
</style>
<script src="util.js"></script>
<script src="lib/jquery-1.8.3.js"></script>
<script src="lib/core-1.0.js"></script>
<script src="lib/shortcut-2.01b.js"></script>
<script src="fan.js"></script>
<script src="legacy_options.js"></script>
<script src="config_viewers_default.js"></script>
<script src="../conf/config_viewers.js"></script>
<script src="../i18n/messages_i18n.js"></script>
<script src="pntserver.js"></script>
<script src="images.js"></script>
<script src="opc-codes.js"></script>
<script>
"use strict";

// This Viewer presents alarms in a grid.
//
// {json:scada} - Copyright 2020 - Ricardo L. Olsen
// Derived from: OSHMI/Open Substation HMI - Copyright 2008-2020 - Ricardo L. Olsen

var L = []; // Event list
var V = []; // Point values
var S = []; // Point values as String
var F = []; // Point quality flags
var T = []; // Alarm time tags
var TAGS = []; // Point tag names
var NPTS = []; // Point numbers by tags names
var SUBS = []; // Grouping level 1 of points (e.g. substation)
var BAYS = []; // Grouping level 2 of points (e.g. bay)
var DCRS = []; // Point description
var ANOTS = []; // Point blocking annotation
var DNOTES = []; // Point documental annotations
var STONS = []; // On status texts
var STOFS = []; // Off status texts
var STONS = []; // On status texts
var STOFS = []; // Off status texts
var LIMSUPS = []; // Analog superior limits for points
var LIMINFS = []; // Analog inferior limits for points
var INVTAGS = []; // List of invalid tags (not found)
var NUM_VAR = 0;        // aqui o servidor informa o número de variações digitais
var NUM_VAR_ANT = 0;    // estado anterior da variável NUM_VAR
var GROUP1_LIST = []; // list of possible group1 values
var ALARMBEEP = 0; // alarm beep status
var BEEP_POINTKEY = -1;
var CNTUPDATES_POINTKEY = -2;

// variáveis para o diálogo de comando
var NPTO, ID, ESTACAO, MODULO, DESC, ST_ON, ST_OFF, CNPTO, CID, CDESC, CST_ON, CST_OFF;
var LIMS, LIMI, HISTER, ALRIN, ANOT, VLNOR, ESTALM, UNIDADE, SIMULACAO = 0;
var ComandoAck = ''; // texto para confirmação do comando 
var ComandoEnviado = '';

// callback function a ser chamada pelo servidor
var cbf_F = function() {}; 

var WebSAGE =
{
g_cbBloqAtu: 'cbBloqAtu',
// g_remoteServer: TabPNTServer,
g_timeOutRefresh: 1000 * TabularViewer_RefreshTime,  // tempo de refresh dos dados
g_timeOutReconhece: 4000, // tempo de refresh após reconhecimento total dos alarmes
g_timeOutFalha: 30000,    // tempo para falha dos dados, caso servidor não responda 
g_toutID: 0,
g_timerID: 0, 
g_showValsIntervalID : 1,
g_timeoutFalhaID: 0,
g_pass: 0,
g_COL_NPONTO: 0,
g_COL_ID: 1,
g_COL_SUBEST: 2,
g_COL_DESCR: 3,
g_COL_EVENTO: 4,
g_COL_FLAGS: 5,
g_COL_SUPCMD: 6,
g_COL_QUALIF: 7,
g_COL_ESTNOR: 8,
g_COL_DATA: 9,
g_COL_DATA_CAMPO: 10,
g_nponto_sup: 0,
g_nponto_cmd: 0,
g_win_cmd: {},
g_cmd_id: 0,
g_filtroSE: '',
g_filtroMOD: '',
g_muda_mod: 0,
g_win_1stdraw: 0,
g_wait_win: 0,
g_datahoraant: '',
g_waitingServer: 0,
g_cbf_MostraSE: function() {},
g_bloqa: 0, 
g_bloqc: 0, 
g_firstdraw: 1,
g_fontsize: 16,  // tamanho das fontes
g_tbl: {},
g_tblhead: {},
g_titulo_janela: "",
g_filt_all_alarms: "TODOS_ANORMAIS",
g_alminfo: [],
g_filterOutList: [],
g_split_line_code: "®æ",
g_split_mod_descr: "~",

// Return value from tag or number
getValue: function(tagornumber) {
  return V[tagornumber] || V[NPTS[tagornumber]] || 0;
},

// Return flags from tag or number
getFlags: function(tagornumber) {
  var f = F[tagornumber] || F[NPTS[tagornumber]];
  if (isNaN(f))
    return 0xa0 | (WebSAGE.getValue(tagornumber) == 0 ? 0x02 : 0x01);
  else return f;
},

// Return inferior limit from tag or number
getInfLim: function(tagornumber) {
  return LIMINFS[tagornumber] || LIMINFS[NPTS[tagornumber]] || 0;
},

// Return superior limit from tag or number
getSupLim: function(tagornumber) {
  return LIMSUPS[tagornumber] || LIMSUPS[NPTS[tagornumber]] || 0;
},

// Return substation from tag or number
getSubstation: function(tagornumber) {
  return SUBS[tagornumber] || SUBS[NPTS[tagornumber]] || "";
},

// Return bay from tag or number
getBay: function(tagornumber) {
  return BAYS[tagornumber] || BAYS[NPTS[tagornumber]] || "";
},

// Return description from tag or number
getDescription: function(tagornumber) {
  return DCRS[tagornumber] || DCRS[NPTS[tagornumber]] || "";
},

// Return alarm time from tag or number
getTime: function(tagornumber) {
  return T[tagornumber] || T[NPTS[tagornumber]] || "";
},

// Return annotation
getAnnotation: function(tagornumber) {
  return ANOTS[tagornumber] || ANOTS[NPTS[tagornumber]] || "";
},

isAlarmsViewer : function()
{
return document.getElementById('FiltroSE').value === WebSAGE.g_filt_all_alarms;
},

mudaFiltro : function()
{
if ( WebSAGE.g_filtroSE != document.getElementById('FiltroSE').value || 
     WebSAGE.g_filtroMod != document.getElementById('FiltroMod').value )  
  {
  WebSAGE.writeElemByIdWnd( window, 'ATUALIZACAO', ' ' );
  WebSAGE.writeElemByIdWnd( window, 'NUMREGS', ' ' );
  clearTimeout( WebSAGE.g_toutID );
  WebSAGE.g_tbl.clearAll();
  document.getElementById("cbMostraCmd").checked = false;
  document.getElementById("cbForaNormal").checked = false;

  WebSAGE.g_filtroSE = document.getElementById('FiltroSE').value;
  WebSAGE.g_filtroMod = document.getElementById('FiltroMod').value;
  L = [];
  if ( WebSAGE.g_filtroSE == '')
    {
        clearTimeout( WebSAGE.g_timeoutFalhaID );
    }
  else
    {
    clearTimeout( WebSAGE.g_toutID );
    WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, 100 );
    WebSAGE.g_muda_mod = 1;
    }
  }
},

// get group1 (stations) list
getGroup1List : function() 
{
    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.Extended_RequestUniqueAttributeValues // read data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1500,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
      AttributeId: OpcAttributeId.ExtendedGroup1
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results){
          console.log("RequestUniqueAttributeValues invalid service response!");
          return;
          }

        // response must have same request handle and be a read response or service fault 
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
            (data.ServiceId !== OpcServiceCode.Extended_ResponseUniqueAttributeValues && data.ServiceId !== OpcServiceCode.ServiceFault)
           ){
          console.log("RequestUniqueAttributeValues invalid or unexpected service response!");
          return;
          }
        if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good ){
          console.log("RequestUniqueAttributeValues service error!");
          return;
        }

        if ("Results" in data.Body)
        data.Body.Results.map(element => {
          if (element.Value.Body !== null && element.Value.Body !== "")
            GROUP1_LIST.push(element.Value.Body);
          return element;
        });
        WebSAGE.mostraSEs();
      })
      .catch(function (error) {
        console.log(error);
      });

},

// mostra lista de estações
mostraSEs : function() 
{
var Lista = GROUP1_LIST;
var i = 0;

Lista.sort();
document.getElementById("SELSE").options.length = 0;

for (var se in Lista)
 {
 if ( Lista[i] != undefined )
   document.getElementById("SELSE").options[i]  = new Option(Lista[i], Lista[i]);
 i++;
 }

// ve se recebeu algum modulo (SE+MODULO) para mostrar pela URL, caso positivo já usa para filtrar  
var se = decodeURI(gup("SUBST"));
var mod = decodeURI(gup("BAY"));

if ( se != "" )
  {
   if ( mod === undefined )
     mod = "";
   document.getElementById('FiltroSE').value = se;
   document.getElementById('FiltroMod').value = mod;
   setTimeout( WebSAGE.mudaFiltro, 10 );
  }

// document.getElementById("SELSE").onChange = WebSAGE.mudaSE; 
},

// Processa mudança da SE selecionada
mudaSE : function() 
{ 
var v = document.getElementById("SELSE").options[document.getElementById("SELSE").selectedIndex].value;
document.getElementById('FiltroSE').value = v ;  
document.getElementById('FiltroMod').value = "";
document.getElementById("SELMOD").options.length = 0;
  
WebSAGE.mudaFiltro();
document.getElementById("SELSE").onchange = function() {};
document.getElementById("SELSE").options[0].selected = 1;  
document.getElementById('SELSE').blur();
},

// mostra lista de opções de móulos
mostraMods : function() 
{
var lmods = [];
var descr = [];
var i;
var cnt;

if ( WebSAGE.isAlarmsViewer() )
  return;
if ( document.getElementById('FiltroSE').value == "" )
  {
  document.getElementById("SELMOD").options.length = 0;
  return;
  }

WebSAGE.g_muda_mod = 0;

// assemble bay list
cnt = 0;
for ( i in L )
  {
  descr = WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_DESCR).trim().split(WebSAGE.g_split_mod_descr);
  if ( lmods.indexOf( descr[0] ) === -1 ) 
    {
    lmods[cnt] = descr[0];
    cnt++;
    }
  }

if ( lmods.length <= 2 ) // if 1 bay only, presents no choice
  return;

lmods[lmods.length] = ""; // 1st option is empty
lmods.sort();
document.getElementById("SELMOD").options.length = 0;

// options list
cnt = 0;
for ( cnt = 0; cnt < lmods.length ; cnt++ )
  {
  document.getElementById("SELMOD").options[cnt] = new Option( lmods[cnt], lmods[cnt] );
  }
},

// Filtra pelo módulo  
mudaMod : function()
{
  document.getElementById('FiltroMod').value =
    document.getElementById("SELMOD").options[document.getElementById("SELMOD").selectedIndex].value;
  WebSAGE.mudaFiltro();
  document.getElementById("SELMOD").options.length = 0;
},

callServer : function() 
{
     clearTimeout( WebSAGE.g_toutID );

     if ( WebSAGE.g_waitingServer ) // se ainda está esperando pelo servidor
       { // dá mais um tempo para rechamar-se e cai fora
         WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh / 2 );
         return;
       }       

    // testa se não está bloqueado
    if ( document.getElementById(WebSAGE.g_cbBloqAtu).checked == false )
      {
        // antes de chamar o servidor
        // prepara uma função que falha todos os pontos se der timeout na chamada do servidor
        clearTimeout( WebSAGE.g_timeoutFalhaID );
        WebSAGE.g_timeoutFalhaID = setTimeout( WebSAGE.falhaTudo, WebSAGE.g_timeOutFalha );
        // prepara função de callback que será chamada pelo script a ser devolvido pelo servidor
        cbf_F = function() 
                  { 
                  clearTimeout( WebSAGE.g_timeoutFalhaID ); 
                  WebSAGE.g_waitingServer = 0; 
                  setTimeout( WebSAGE.getServerStatus, WebSAGE.g_timeOutRefresh/2 );
                  setTimeout( WebSAGE.showVals, 100 ); 
                  };
        // sinaliza que vai esperar resposta do servidor 
        WebSAGE.g_waitingServer = 1;

        Ventoinha.pulse();

        // chama o servidor, executa o script devolvido
        if (WebSAGE.isAlarmsViewer())
          WebSAGE.getRealtimeFilteredData("", "", true, cbf_F);
        else  
          WebSAGE.getRealtimeFilteredData(WebSAGE.g_filtroSE, WebSAGE.g_filtroMod, true, cbf_F);
        WebSAGE.g_pass++;        
        WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
      }
},

  // pega status do servidor, variável ALARMBEEP
getServerStatus: function() {

WebSAGE.getRealtimeData( [BEEP_POINTKEY, CNTUPDATES_POINTKEY], true,
  prop => { 
  // se mudou estado de alarme, poderia consultar antes
  if ( NUM_VAR_ANT != WebSAGE.getValue(CNTUPDATES_POINTKEY) ) {
  }

  ALARMBEEP = WebSAGE.getValue(BEEP_POINTKEY);
  NUM_VAR_ANT = WebSAGE.getValue(CNTUPDATES_POINTKEY);
  if ( ALARMBEEP )
      {
      document.getElementById( "SILENCIA_ID" ).style.display = "";
      }
    else
      {
      document.getElementById( "SILENCIA_ID" ).style.display = "none";
      }
  });   
},

getRealtimeFilteredData: function (group1Filter, group2Filter, onlyAlarms, callbacksuccess) {

  var ContentFilter = []
    var ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }

    if ( typeof group1Filter === "string" && group1Filter.trim()!=="" ){
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "group1"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: group1Filter});
      ContentFilter.push(ContFiltElem);
    }
    if ( typeof group2Filter === "string" && group2Filter.trim()!=="" ){
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "group2"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: group2Filter});
      ContentFilter.push(ContFiltElem);
    }
    if (onlyAlarms){
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "alarmed"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: true});
      ContentFilter.push(ContFiltElem);
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "invalid"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: false});
      ContentFilter.push(ContFiltElem);
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "persistentAlarms"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: true});
      ContentFilter.push(ContFiltElem);
    }

   // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.ReadRequest // read data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1500,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        Namespace: OpcNamespaceMongodb, // directs query to mongodb instead of postgresql
        ContentFilter: ContentFilter,
        MaxAge: 0,
        TimestampsToReturn: TimestampsToReturn.Both
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle){
          console.log("ReadRequest invalid service response!");
          return;
          }

        // response must have same request handle and be a read response or service fault 
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
            (data.ServiceId !== OpcServiceCode.ReadResponse && data.ServiceId !== OpcServiceCode.ServiceFault)
           ){
          console.log("ReadRequest invalid or unexpected service response!");
          return;
          }
        if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good && 
             data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.GoodNoData){
          console.log("ReadRequest service error!");
          return;
        }

        if (!data.Body.Results)
          return;

        WebSAGE.Pass++;

        L = [];
        var prop;
        if ("Results" in data.Body)
        data.Body.Results.map(element => {
          if (typeof element.StatusCode == "number" && element.StatusCode != 0) // reject a bad result
            return;
          if (element.NodeId.IdType != OpcKeyType.String)
            return;

          prop = element._Properties;          
          var pointKey = prop._id;

          var dateAlarm = "";
          if (prop.alarmed && prop.timeTagAlarm!==null)
          if ((new Date(prop.timeTagAlarm)).getFullYear()>1980)
            dateAlarm = (new Date(prop.timeTagAlarm)).toLocaleString();
          var dateField = "";
          if ("SourceTimestamp" in element && element.SourceTimestamp!==null){
            var dt = new Date(element.SourceTimestamp);
            dateField = dt.toLocaleString() + "." + ("00" + dt.getUTCMilliseconds()).slice(-3);
          }

          var flags = (element.Value.Quality!==OpcStatusCodes.Good?0x80:0x00);
          if ( element.Value.Type === OpcValueTypes.Double )
            flags |= 0x20;
          if (prop.alarmed )
            flags |= 0x100;
          var persistent = "";
          if (prop.alarmState === 1 && element.Value.Body === true  ||
              prop.alarmState === 0 && element.Value.Body === false )
            persistent = "P";

          if (prop.origin!=="command" && (prop._id > 0 || group1Filter === "_System" ) )
          L.push(
            "" + pointKey + WebSAGE.g_split_line_code +
            element.NodeId.Id + WebSAGE.g_split_line_code +
            prop.group1 + WebSAGE.g_split_line_code +
            prop.group2 + WebSAGE.g_split_mod_descr + prop.ungroupedDescription  + WebSAGE.g_split_line_code +
            prop.valueString  + WebSAGE.g_split_line_code +
            flags + WebSAGE.g_split_line_code + // flags
            prop.commandOfSupervised + WebSAGE.g_split_line_code + 
            "" + prop.priority + (prop.alarmed?"L":"") + persistent +
              (prop.isEvent?"E":"") + 
              (prop.commandOfSupervised!==0?"K":"") + 
              (prop.origin==="manual"?"M":"") + 
              (("timeTagAtSourceOk" in prop && prop.timeTagAtSourceOk === "false")?"T":"") +
              (element.Value.Quality!==OpcStatusCodes.Good?"F":"") +
              WebSAGE.g_split_line_code +
            0  + WebSAGE.g_split_line_code + // normal state
            dateAlarm + WebSAGE.g_split_line_code +
            dateField
          )

          if (element.Value.Type === OpcValueTypes.Boolean) {
            V[pointKey] = element.Value.Body?0:1;
            F[pointKey] = (element.Value.Body ? 0x02 : 0x01);
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
          else if (element.Value.Type === OpcValueTypes.Double) {
            V[pointKey] = element.Value.Body;
            F[pointKey] = 0x20;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
          else if (element.Value.Type === OpcValueTypes.String) { 
            V[pointKey] = parseFloat(element.Value.Body);
            F[pointKey] = 0x00;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = element.Value.Body;
          }
          F[pointKey] |= (element.Value.Quality & 0x80000000 ? 0x80 : 0x00) | 
                         (prop.alarmed ? 0x100 : 0x000) |
                         (("annotation" in prop && prop.annotation!=="")? 0x200 : 0x000);

          TAGS[pointKey] = element.NodeId.Id;
          NPTS[element.NodeId.Id] = pointKey;
          if (typeof prop.notes == "string") 
            DNOTES[pointKey] = prop.notes;
          if (typeof prop.annotation == "string") 
            ANOTS[pointKey] = prop.annotation;
          if (typeof prop.description == "string") {
            SUBS[pointKey] = prop.group1;
            BAYS[pointKey] = prop.group2;
            DCRS[pointKey] = (prop.ungroupedDescription!="")? prop.ungroupedDescription : prop.description;
            if (isNaN(prop.hiLimit) || prop.hiLimit === null)
              LIMSUPS[pointKey] = Infinity;
            else  
              LIMSUPS[pointKey] = prop.hiLimit;
            if (isNaN(prop.loLimit) || prop.loLimit === null)
              LIMINFS[pointKey] = -Infinity;
            else
              LIMINFS[pointKey] = prop.loLimit;
            STONS[pointKey] = prop.stateTextTrue;
            STOFS[pointKey] = prop.stateTextFalse;
          }
          return element;
        });
        if ( typeof callbacksuccess == "function" )
           callbacksuccess(prop);
      })
      .catch(function (error) {
        console.log(error);
      });
  },

// obtains realtime data from the server translating it to 
// internal data structures the call the SVG screen update routine
getRealtimeData: function (querykeys, askinfo, callbacksuccess) {
  if (typeof querykeys.length !== "number" || querykeys.length == 0)
      return;
  
  // use OPC web hmi protocol https://prototyping.opcfoundation.org/
  var ServiceId = OpcServiceCode.ReadRequest // read data service
  var RequestHandle = Math.floor(Math.random() * 100000000)
  var req = {
    ServiceId: ServiceId,
    Body: {
      RequestHeader: {
        Timestamp: new Date().toISOString(),
        RequestHandle: RequestHandle,
        TimeoutHint: 1500,
        ReturnDiagnostics: 2,
        AuthenticationToken: null
      },
      MaxAge: 0,
      TimestampsToReturn: TimestampsToReturn.Both
    }
  }

  var NodesToRead = [];
  querykeys.map(element => {
    var IdType, Id;
    if (isNaN(parseInt(element))) { // a tag
      IdType = OpcKeyType.String;
      Id = element;
    }
    else { // a numeric key
      IdType = OpcKeyType.Numeric;
      Id = parseInt(element);
    }
    NodesToRead.push({
      "NodeId": {
        "IdType": IdType,
        "Id": Id,
        "Namespace": OpcNamespaceMongodb
      },
      "AttributeId": (askinfo === true ? OpcAttributeId.Description : OpcAttributeId.Value)
    });
    return element;
  });
  req.Body.NodesToRead = NodesToRead;

  fetchTimeout("/Invoke/", 1500, {
    method: "POST",
    body: JSON.stringify(req),
    headers: {
      "Content-Type": "application/json"
    }
  })
    .then(function (response) {
      return response;
    })
    .then(response => response.json())
    .then(data => {
      if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results){
        console.log("ReadRequest invalid service response!");
        return;
        }

      // response must have same request handle and be a read response or service fault 
      if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
          (data.ServiceId !== OpcServiceCode.ReadResponse && data.ServiceId !== OpcServiceCode.ServiceFault)
          ){
        console.log("ReadRequest invalid or unexpected service response!");
        return;
        }
      if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good ){
        console.log("ReadRequest service error!");
        return;
      }

      WebSAGE.Pass++;

      var prop;
      if ("Results" in data.Body)
      data.Body.Results.map(element => {
        if (typeof element.StatusCode == "number" && element.StatusCode != 0) // reject a bad result
          return;
        if (element.NodeId.IdType != 1)
          return;
        prop = element._Properties;          
        var pointKey = prop._id;

        if (element.Value.Type === OpcValueTypes.Boolean) {
            V[pointKey] = element.Value.Body?0:1;
            F[pointKey] = (element.Value.Body ? 0x02 : 0x01);
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
        else if (element.Value.Type === OpcValueTypes.Double) {
            V[pointKey] = element.Value.Body;
            F[pointKey] = 0x20;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
        else if (element.Value.Type === OpcValueTypes.String) { 
            V[pointKey] = parseFloat(element.Value.Body);
            F[pointKey] = 0x00;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = element.Value.Body;
          }
        F[pointKey] |= (element.Value.Quality & 0x80000000 ? 0x80 : 0x00) | 
                        (prop.alarmed ? 0x100 : 0x000) |
                        (("annotation" in prop && prop.annotation!=="")? 0x200 : 0x000);

        TAGS[pointKey] = element.NodeId.Id;
        NPTS[element.NodeId.Id] = pointKey;
        if (typeof prop.description == "string") {
          SUBS[pointKey] = prop.group1;
          BAYS[pointKey] = prop.group2;
          DCRS[pointKey] = (prop.ungroupedDescription!="")? prop.ungroupedDescription : prop.description;
          if (isNaN(prop.hiLimit) || prop.hiLimit === null)
            LIMSUPS[pointKey] = Infinity;
          else  
            LIMSUPS[pointKey] = prop.hiLimit;
          if (isNaN(prop.loLimit) || prop.loLimit === null)
            LIMINFS[pointKey] = -Infinity;
          else
            LIMINFS[pointKey] = prop.loLimit;
          STONS[pointKey] = prop.stateTextTrue;
          STOFS[pointKey] = prop.stateTextFalse;
          ANOTS[pointKey] = prop.annotation;
          DNOTES[pointKey] = prop.notes;
        }
        return element;
      });
      if ( typeof callbacksuccess == "function" )
          callbacksuccess(prop);
    })
    .catch(function (error) {
      console.log(error);
    });
  },

// acknowledges or quits event alarm
ackAlarm: function( almquit, aggregate, pointId)
{
if (almquit === undefined)
  almquit = 0;
aggregate = 0;
if (pointId === undefined)
  pointId = 0;

  var action = 0;
  if (almquit === 0 && pointId === 0){ // ack all alarms
     action = OpcAcknowledge.AckAllAlarms | OpcAcknowledge.SilenceBeep;
  }
  if (almquit === -1){ // only silcence beep
     action = OpcAcknowledge.SilenceBeep;
  }
  if (almquit === 0 && aggregate === 0 && pointId !== 0){ // ack one alarm
     action = OpcAcknowledge.AckOneAlarm | OpcAcknowledge.SilenceBeep;
  }
    var IdType = OpcKeyType.Numeric;
    if ( isNaN(parseInt(pointId)) )
      IdType = OpcKeyType.String;
    else  
      pointId = parseInt(pointId);

    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.WriteRequest // write data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1000,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        NodesToWrite: [
          {
            NodeId: {
              IdType: IdType, // type: numeric or string key
              Id: pointId, // key for the point
              Namespace: OpcNamespaceMongodb
            },
            AttributeId: OpcAttributeId.ExtendedAlarmEventsAck, // OPC attribute to write: Alarm Ack
            Value: {
              Type: OpcValueTypes.Integer,
              Body: action
            }
          }
        ]
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if ( (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results) ||
             (data.ServiceId !== OpcServiceCode.WriteResponse || data.Body.ResponseHeader.RequestHandle !== RequestHandle) ||
             (data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good && data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.GoodNoData)) {
              console.log("Ack Alarm Error!");
              return;
        }

        // success
      })
      .catch(err => {
        console.log(err);
      });

},

ackAllAlarms: function(rec_apaga)
{
    WebSAGE.ackAlarm(0, 0, 0);
    // clearTimeout(WebSAGE.g_toutID);
	  // WebSAGE.g_toutID = setTimeout(WebSAGE.callServer, WebSAGE.g_timeOutReconhece);
},

applyTableStyle: function()
{
// assemble the filterout list
WebSAGE.g_filterOutList = [];
for ( var substorpri in WebSAGE.g_alminfo ) 
   {
     if ( WebSAGE.g_alminfo.hasOwnProperty(substorpri) ) 
        {
        if ( WebSAGE.isAlarmsViewer() )
           {
           if (  WebSAGE.g_alminfo[substorpri].filterout )
              {
              WebSAGE.g_filterOutList.push(substorpri);
              $( '#DIV_' + substorpri ).css('opacity', '.25');
              }
           else
              {
              $( '#DIV_' + substorpri ).css('opacity', '1' );         
              }              
           }
        }
   }

// reapply line styles
for( var i = 0; i < L.length; i++ )
   {
   WebSAGE.EstiloLinha( i, 0 );   
   }
},

// Mostra os eventos recebidos
showVals: function()
  {
  var dbg = 0;
  
  try
    {
    var qualif, i, cntsb, cntpr, xleft, ytop;
    var tam = L.length;  
    var sb;
    
    // quando houver o que mostrar e não for modo de todas anormalidades, mostra controles de filtro anormais e comandáveis
    if ( tam > 0 )
    if ( document.getElementById('FiltroSE').value !== "TODOS_ANORMAIS" )
    if ( document.getElementById("OPC_CMDANORM").style.display === "none" )   
      {
        document.getElementById("OPC_CMDANORM").style.display = "inline";    
      }

    clearTimeout( WebSAGE.g_toutID ); // para de chamar o servidor
    
    if ( window.parent.document.getElementById('almiframe'))
    if ( window.parent.document.getElementById('almiframe').contentDocument === document )
      {
      var box = window.parent.document.getElementById('almiframe');
      var h = L.length * 13;
      if ( h > 40 )
         h = 40;
      else   
      if ( box.height !== h ) 
        box.height = h;                  
      if ( box.height < h ) 
        box.height = h;                  
      }
    
    var chkcmd = document.getElementById("cbMostraCmd").checked;
    var chknor = document.getElementById("cbForaNormal").checked;

    dbg = 1;
    
    // zeroes the alarm count and minimum priority
    for ( var subst in WebSAGE.g_alminfo ) 
     {
     if ( WebSAGE.g_alminfo.hasOwnProperty(subst) ) 
        {
        WebSAGE.g_alminfo[subst].countack = 0;
        WebSAGE.g_alminfo[subst].countnack = 0;
        WebSAGE.g_alminfo[subst].minpriorack = 10;
        WebSAGE.g_alminfo[subst].minpriornack = 10;
        }
     }
    
    // só mostra se vier uma linhas diferente da que já havia
    for( i = 0; i < tam; i++ )
      {
      if ( i >= WebSAGE.g_tbl.getRowsNum() )
        {
        dbg = 1.6; // bug intermitente do chrome

        WebSAGE.g_tbl.addRow( i, L[i] );
        // WebSAGE.EstiloLinha( i, 0 );
        qualif=WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_FLAGS);
        if ( (WebSAGE.g_tbl.cellsx(i, WebSAGE.g_COL_QUALIF).indexOf("I")!=-1) || // alarme inibido
             (WebSAGE.g_tbl.cellsx(i, WebSAGE.g_COL_QUALIF).indexOf("J")!=-1) // comando
           ) 
          WebSAGE.g_tbl.setRowHidden(i,true);
        else
          {
          WebSAGE.g_tbl.setRowColor(i,TabularViewer_LineColor);
  
          if ( qualif & 0x100 )
            {
            WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_AlmTxtColor);
            }
          else  
          if ( qualif & 0x80 )
            WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_FailTxtColor);
          else  
            WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_AckTxtColor);
            
          WebSAGE.g_tbl.setRowHidden(i,false);
          }          
       }
      else
        {
        if ( L[i]!=WebSAGE.g_tbl.getUserData(i) ) // enquanto for igual, mantém
           {
  
            WebSAGE.g_tbl.changeRow(i, L[i]);
            
            qualif=WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_FLAGS);
               
            if ( (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_QUALIF).indexOf("I")!=-1) || // alarme inibido
                 (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_QUALIF).indexOf("J")!=-1) || // comando
                 (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_SUPCMD)==0 && chkcmd ) ||
                 (!( parseInt(WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_FLAGS)) & 0x800 ) && chknor) 
               )    
              WebSAGE.g_tbl.setRowHidden(i,true);
            else
              {         
              WebSAGE.g_tbl.setRowColor(i,TabularViewer_LineColor);
        
              if ( qualif & 0x100 )
                WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_AlmTxtColor);
              else  
              if ( qualif & 0x80 )
                WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_FailTxtColor);
              else  
                WebSAGE.g_tbl.setRowTextStyle(i,'color: '+TabularViewer_AckTxtColor);
                
              WebSAGE.g_tbl.setRowHidden(i,false);         
              }  
           }
        }
        
     // account alarms by priority and substation, leave out commands and inhibited alarms
     if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("J") == -1 &&
          WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("I") == -1 
        )
       {      
       var pri = WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).charAt(0);
       // count alarms by priority
       if ( typeof( WebSAGE.g_alminfo[ pri ] ) === 'undefined' )
         {
         WebSAGE.g_alminfo[ pri ] =  { countnack: 0, 
                                       countack: 0, 
                                       filterout: false, 
                                       minpriorack: 10, 
                                       minpriornack: 10, 
                                       is_subst: false };
         }
       if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 )
         {
           WebSAGE.g_alminfo[ pri ].countnack += 1;
           WebSAGE.g_alminfo[ pri ].minpriornack = pri;
         }  
       else
         {
           WebSAGE.g_alminfo[ pri ].countack += 1;
           WebSAGE.g_alminfo[ pri ].minpriorack = pri;
         }         

       // count alarms by module (not substation)
       // accessible on parent of iframe via document.getElementById("almiframe").contentWindow.WebSAGE.g_alminfo
       sb = WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_DESCR ).split("~")[0];
       if ( typeof( WebSAGE.g_alminfo[ sb ] ) === 'undefined' )
         {
         WebSAGE.g_alminfo[ sb ] =  { countnack: 0, 
                                      countack: 0, 
                                      filterout: false, 
                                      minpriorack: 10, 
                                      minpriornack: 10, 
                                      is_subst: true };
         }
       // count and verify minimum priority of unacknowledged and acknowledge alarms
       if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 )
         {
           WebSAGE.g_alminfo[ sb ].countnack += 1;
           if ( pri < WebSAGE.g_alminfo[ sb ].minpriornack )
             WebSAGE.g_alminfo[ sb ].minpriornack = pri;
         }
       else  
         {
           WebSAGE.g_alminfo[ sb ].countack += 1;
           if ( pri < WebSAGE.g_alminfo[ sb ].minpriorack )
             WebSAGE.g_alminfo[ sb ].minpriorack = pri;
         }
       } 
     }
                  
    while ( tam < WebSAGE.g_tbl.getRowsNum() ) // para caso onde ficam entrando e saindo linhas (qdo mostra tudo anormal)) 
     {
        WebSAGE.g_tbl.delRow( WebSAGE.g_tbl.getRowsNum() - 1 );
     }

    // se a janela info estiver aberta, fica atualizando o ponto 
    if ( WebSAGE.g_win_cmd )
    if ( WebSAGE.g_win_cmd.window )
    if ( typeof(WebSAGE.g_win_cmd.window) == 'object' )
    if ( typeof(WebSAGE.g_win_cmd.window.closed) != 'undefined' )
       if ( ! WebSAGE.g_win_cmd.window.closed )
        {
        getScript( WebSAGE.g_remoteServer + '?P=' + WebSAGE.g_nponto_sup );
        clearTimeout( WebSAGE.g_timerID );
        WebSAGE.g_timerID = setTimeout( WebSAGE.showValsInfo2, 100 );
        }
        
    if ( WebSAGE.g_muda_mod == 1 )
      // atualiza opções de módulo
      setTimeout( WebSAGE.mostraMods, 100 );
      
    WebSAGE.writeElemByIdWnd( window, 'ATUALIZACAO', Data );

    WebSAGE.writeElemByIdWnd( window, 'NUMREGS', WebSAGE.g_tbl.getRowsNum()+" Registros" );
  
    if ( gup("SOCMD")==1 && document.getElementById("cbMostraCmd").checked!=true && WebSAGE.g_firstdraw)
      document.getElementById("cbMostraCmd").click();
    
    if ( WebSAGE.g_firstdraw == 1 )
      WebSAGE.g_firstdraw = 0;  
  
    Ventoinha.pulse(".");
    }
catch (e)
    {
    // o CHROME gera exceptions aqui de vez em quando!
    // 

    Ventoinha.pulse("E");    
    }    

  // próximo refresh
  WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
  },

// função auxiliar para escrever dados na janela de comando pelo id do objeto
writeElemByIdWnd: function(win, id, txt) {
    win.$("#" + id).text(txt);
  },
  
// falha todos os dados caso servidor pare de atualizar por um tempo  
falhaTudo: function ()
  {
  WebSAGE.g_tbl.clearAll();
  WebSAGE.g_tbl.addRow( 0, ",,," + Msg.FalhaWebserver + ",,,,", 0 );
  L = [];
  WebSAGE.g_waitingServer = 0;
  
  // vai tentar de novo
  clearTimeout( WebSAGE.g_toutID );
  WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
  },

doSilenceBeep: function() 
{
    WebSAGE.ackAlarm(-1);
},

EstiloLinha: function (id, rec)
{
try
  {
      var stl='';

      if ( rec == 2 )
          stl = 'color: green;';
      else      
      if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("L")==-1 || rec )
          { // reconhecido
            stl = 'color: ' + TabularViewer_AckTxtColor + ';';
          }
      else    
          { // não reconhecido (alarmado)
            stl = 'color: ' + TabularViewer_AlmTxtColor + ';';
          }

      WebSAGE.g_tbl.setRowTextStyle( id, stl );
      
  }
catch ( e ) 
  { }        
},

// linha selecionada: reconhece  
doOnRowSelected: function ( evt )
  {
    var rec = false;
    var row = 0;
    
    if ( evt === 0 )
      {
          row = 0;
          rec = true;
      }
    else  
      {
          row = evt.currentTarget.rowIndex - 1 ;
          if ( evt.ctrlKey )
            rec = false;
          else 
            rec = true;
      }

    if ( evt.which == 2 )
      rec = true;
  
    var Qualif = WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_QUALIF );    
    
    // RECONHECE 
    if ( rec )
      {
      if ( WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_FLAGS) & 0x100 )
        {
        WebSAGE.ackAlarm(0, 0, WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_NPONTO ) );

        // tira o L do qualificador          
        WebSAGE.g_tbl.cellsSetValue( row, WebSAGE.g_COL_QUALIF, Qualif.replace("L","") );
        WebSAGE.EstiloLinha( row, 1 );
        }          
      }          
    else          
      {
      // somente silencia alarme
      if ( WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_FLAGS) & 0x100 )
          WebSAGE.doSilenceBeep();
      WebSAGE.janelaInfo( WebSAGE.g_tbl.cellsx(row, WebSAGE.g_COL_NPONTO) );
      } 
  },

mostraTodosAnormais: function()
  {
  document.getElementById('FiltroSE').value = "TODOS_ANORMAIS";
  WebSAGE.g_titulo_janela = Msg.NomeVisorAnormais + " - " +  Msg.NomeProduto + " - " + Msg.VersaoProduto;
  document.title = "."; // necessário devido a um bug do chromium!
  document.title = WebSAGE.g_titulo_janela;
  
  LoadFavicon( Imgs.FAVICONANORM );
  WebSAGE.mudaFiltro();
  document.getElementById("cbMostraCmd").checked = false;
  document.getElementById("cbForaNormal").checked = false;
  document.getElementById("OPC_CMDANORM").style.display = "none";
  document.getElementById("OPC_FILTROS").style.display = "none";
  
  document.getElementById("IMGTABULAR").style.display = "none";
  document.getElementById("ANORM_ID").style.display = "";
},

mostraForaNormal: function()
  {
    var chknor = document.getElementById("cbForaNormal").checked;
    
    for(var i in L)
      {
        if ( ( !( parseInt(WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_FLAGS)) & 0x800 )  
             && 
             chknor 
             )
            || (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_QUALIF).indexOf("J")!=-1)  // comando
         )
        WebSAGE.g_tbl.setRowHidden(i,true);
        else
           WebSAGE.g_tbl.setRowHidden(i,false);
      }

    if ( chknor )  // enquanto estiver selecionado, segue filtrando
       {
       WebSAGE.g_bloqc=1; // bloqueia para evitar refiltro pelo comando
       document.getElementById("cbMostraCmd").checked=false;
       }
},
      
mostraCmd: function()
  {
    // pára atualização 
    clearTimeout(WebSAGE.g_toutID);

    var chk = document.getElementById("cbMostraCmd").checked;
    
    for(var i in L)
    {
    if (  
        (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_QUALIF).indexOf("J")!=-1)  // comando
          || (WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_SUPCMD)==0 && chk ) 
         )
        WebSAGE.g_tbl.setRowHidden(i,true);
    else
        WebSAGE.g_tbl.setRowHidden(i,false);
    }

    if ( chk ) 
       {
       WebSAGE.g_bloqa=1; // bloqueia para evitar refiltro pelo estado anormal
       document.getElementById("cbForaNormal").checked=false;
       }
   
   // atualiza após 2s
   WebSAGE.g_toutID = setTimeout(WebSAGE.callServer, 2000);
},
  
fontSize: function(updn)
{
    if ( updn==1 && WebSAGE.g_fontsize<30 ) 
      WebSAGE.g_fontsize=parseInt(WebSAGE.g_fontsize)+1;
    else   
    if ( updn==0 && WebSAGE.g_fontsize>16 ) 
      WebSAGE.g_fontsize=parseInt(WebSAGE.g_fontsize)-1;
    else
      return;  

    // escreve tamanho da fonte em cookie
    var date = new Date();
    date.setTime(date.getTime()+(3000*24*60*60*1000));
    document.cookie = 'abx_fontsize=' + WebSAGE.g_fontsize + '; expires=' + date.toGMTString();
    
    // recarrega a página
    window.location.reload();        
},

tbl_prepare: function()
  {
    WebSAGE.g_tbl=document.getElementById('TBLEVE');
    WebSAGE.g_tblhead=document.getElementById('TBLHEAD');

    WebSAGE.g_tbl.setColAlign =
        function ( vals ) 
        {  
        };

    WebSAGE.g_tbl.setInitWidths =
        function ( vals ) 
        {  
        WebSAGE.g_tbl.larguraCol = [];	
        var varr = vals.split(',');	
        for (var i=0; i < varr.length; i++)
           {
           WebSAGE.g_tbl.larguraCol[i] = varr[i];
           }
        };

    WebSAGE.g_tbl.setHeader = 
        function ( vals ) 
        {  
        var varr = vals.split(',');	

        if ( WebSAGE.g_tbl.rows.length == 0 )
            WebSAGE.g_tbl.insertRow( 0 );

        for (var i=0; i < varr.length; i++)
        {
           WebSAGE.g_tbl.rows[0].insertCell(i);
           WebSAGE.g_tbl.rows[0].cells[i].innerHTML=varr[i];

           WebSAGE.g_tblhead.rows[0].insertCell(i);
           WebSAGE.g_tblhead.rows[0].cells[i].innerHTML=varr[i];

           if ( WebSAGE.g_tbl.larguraCol[i] == 0 )
              {
              WebSAGE.g_tbl.rows[0].cells[i].style.display = 'none';
              WebSAGE.g_tblhead.rows[0].cells[i].style.display = 'none';
              }
            WebSAGE.g_tbl.setColWidth(i, WebSAGE.g_tbl.larguraCol[i] );
        } 

        while ( WebSAGE.g_tbl.rows[0].cells.length > varr.length )
            WebSAGE.g_tbl.rows[0].deleteCell(WebSAGE.g_tbl.rows[0].cells.length-1);

        // acerta tamanho dos campos se a fonte for maior que a padrão
        if (WebSAGE.g_fontsize>16)
            {
            for ( i=0; i < varr.length; i++ )
                WebSAGE.g_tbl.setColWidth( i, (WebSAGE.g_tbl.getColWidth(i) * WebSAGE.g_fontsize)/16);
            }
            
        WebSAGE.g_tbl.rows[0].style.display = 'none';
        };

    WebSAGE.g_tbl.addRow = 
        function ( line, vals ) 
        {  
        line++;	
        var varr = vals.split(WebSAGE.g_split_line_code);	
        WebSAGE.g_tbl.insertRow( line );
        WebSAGE.g_tbl.rows[line].onclick = WebSAGE.doOnRowSelected;
        WebSAGE.g_tbl.rows[line].ondblclick = WebSAGE.doOnRowSelected;
                    
        for (var i=0; i < varr.length; i++)
           {
           WebSAGE.g_tbl.rows[line].insertCell(i);
           WebSAGE.g_tbl.rows[line].cells[i].innerHTML = varr[i];
           WebSAGE.g_tbl.rows[line].cells[i].style.borderBottom = '1px solid ' + ScreenViewer_AlmBoxGridColor;
           if ( WebSAGE.g_tbl.larguraCol[i] == 0 )
             WebSAGE.g_tbl.rows[line].cells[i].style.display = 'none';
          else
             WebSAGE.g_tbl.rows[line].cells[i].width = WebSAGE.g_tbl.larguraCol[i];  
           } 
        WebSAGE.g_tbl.rows[line].userData = vals;
        };

    WebSAGE.g_tbl.changeRow = 
        function ( line, vals ) 
        {  
        line++;	
        var varr = vals.split(WebSAGE.g_split_line_code);	
        
        // equalizes number of columns
        while ( varr.length > WebSAGE.g_tbl.rows[line].cells.length )             
           {
		   var cell = WebSAGE.g_tbl.rows[line].insertCell( -1 );
		   var col = WebSAGE.g_tbl.rows[line].cells.length - 1;
           cell.style.borderBottom = '1px solid ' + TabularViewer_GridColor;
           if ( WebSAGE.g_tbl.larguraCol[col] == 0 )
             cell.style.display = 'none';
           else
             cell.width = WebSAGE.g_tbl.larguraCol[col];  
	       }
         
        for ( var i = 0; i < varr.length; i++ )
           {
           WebSAGE.g_tbl.rows[line].cells[i].innerHTML = varr[i];
           } 
        WebSAGE.g_tbl.rows[line].userData = vals;
        };

    WebSAGE.g_tbl.copyRowContent = 
        function ( linefrom, lineto ) 
        {
        linefrom++;	lineto++;
        for (var i=0; i < WebSAGE.g_tbl.rows[0].cells.length; i++)
          {
          WebSAGE.g_tbl.rows[lineto].cells[i].innerHTML=WebSAGE.g_tbl.rows[linefrom].cells[i].innerHTML;
          } 
        WebSAGE.g_tbl.rows[lineto].userData=WebSAGE.g_tbl.rows[linefrom].userData;
        };

    WebSAGE.g_tbl.getUserData = 
        function ( line ) 
        {
            line++;
            return WebSAGE.g_tbl.rows[line].userData;
        };

    WebSAGE.g_tbl.getRowsNum = 
        function ( ) 
        {
            return WebSAGE.g_tbl.rows.length-1;
        };

    WebSAGE.g_tbl.getColsNum = 
        function ( ) 
        {
            return WebSAGE.g_tblhead.rows[0].cells.length;
        };

    WebSAGE.g_tbl.delRow = 
        function ( row ) 
        {
            row++;
            return WebSAGE.g_tbl.deleteRow(row);
        };

    WebSAGE.g_tbl.setRowColor = 
        function ( row, color ) 
        {
            row++;
            WebSAGE.g_tbl.rows[row].style.color = color;
        };

    WebSAGE.g_tbl.setRowHidden = 
        function ( row, hide ) 
        {
            row++;
            if ( hide )
              WebSAGE.g_tbl.rows[row].style.display = 'none';
            else  
              WebSAGE.g_tbl.rows[row].style.display = '';
        };

    WebSAGE.g_tbl.cellsx = 
        function ( row, col ) 
        {
            row++;
            return WebSAGE.g_tbl.rows[row].cells[col].innerHTML;
        };      
      
    WebSAGE.g_tbl.cellsSetValue = 
        function ( row, col, value ) 
        {
            row++;
            return WebSAGE.g_tbl.rows[row].cells[col].innerHTML=value;
        };      

    WebSAGE.g_tbl.getColWidth = 
        function ( col ) 
        {
            return WebSAGE.g_tbl.larguraCol[col];
        };

    WebSAGE.g_tbl.setColWidth = 
        function ( col, width ) 
        {
            WebSAGE.g_tbl.larguraCol[col] = width;
            try { // avoid IE error when width=0
              WebSAGE.g_tblhead.rows[0].cells[col].width = width;
            }
            catch (err){
              WebSAGE.g_tblhead.rows[0].cells[col].width = 1 + width;
            }
            WebSAGE.g_tblhead.rows[0].cells[col].style.display = (width==0)?'none':'';
            for (var i=0; i < WebSAGE.g_tbl.rows.length; i++) 
              {
                  try { // avoid IE error width=0
                    WebSAGE.g_tbl.rows[i].cells[col].width = width;
                    }
                    catch (err){
                      WebSAGE.g_tbl.rows[i].cells[col].width = 1 + width;
                    }
                  WebSAGE.g_tbl.rows[i].cells[col].style.display = (width==0)?'none':'';
              }
        return 0;
        };

    WebSAGE.g_tbl.setStyle = 
        function ( ss_header, ss_grid, ss_selCell, ss_selRow ) 
        {
            WebSAGE.g_tbl.style.cssText = ss_grid;
            return 0;
        };

    WebSAGE.g_tbl.setRowTextStyle = 
        function ( row, stl ) 
        {
            row++;
            WebSAGE.g_tbl.rows[row].style.cssText = stl;
            return 0;
        };
        
    WebSAGE.g_tbl.clearAll = 
        function ( ) 
        {
            while (WebSAGE.g_tbl.rows.length>1) 
            WebSAGE.g_tbl.deleteRow(1);
        };      
  },

showInfo: function()
  {
    var wid=WebSAGE.g_tbl.getColWidth(WebSAGE.g_COL_NPONTO);
    if ( wid == 0 )
      {
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_NPONTO, 60*WebSAGE.g_fontsize/16 );
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_ID, 230*WebSAGE.g_fontsize/16 );    
      WebSAGE.g_tbl.witdh='1500px';
      WebSAGE.g_tblhead.witdh='1500px';
      }
    else  
      {
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_NPONTO, 0 );
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_ID, 0 );    
      WebSAGE.g_tbl.witdh='1200px';
      WebSAGE.g_tblhead.witdh='1200px';
      }
  },

init: function()
{
    WebSAGE.tbl_prepare();

    WebSAGE.g_titulo_janela = Msg.NomeVisorTabular + " - " +  Msg.NomeProduto + " - " + Msg.VersaoProduto;
    document.title = "."; // necessário devido a um bug do chromium!
    document.title = WebSAGE.g_titulo_janela;

    // vai nos objetos com 'id' e coloca como 'title' a mensagem correspondente de Titles, carrega as imagens (de images.js)        
    $('img[id]').attr('src', function(index) { return Imgs[this.id]; } );
    $('img[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('span[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('input[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('select[id]').attr('title', function(index) { return Titles[this.id]; } );

    var gridstyle = "outline:none;cursor:pointer;font-weight:bold;border:0px;white;padding:0px;margin-left:2px;";
    gridstyle = gridstyle + 'table-layout:fixed;';
    gridstyle = gridstyle + 'color:' + TabularViewer_AckTxtColor + ';'; 
    gridstyle = gridstyle + 'font-family:' + "arial,helvetica" + ';'; 
    WebSAGE.g_tbl.setStyle(';', 'height:auto;font-size:' + "10" + 'px;' + gridstyle,';',';');
    WebSAGE.g_tbl.setInitWidths("0,0,0,130,110,0,0,0,0,200,0");
    WebSAGE.g_tbl.setHeader(Msg.TabNomesColunas);

    $('#gridbox').css('background-color', ScreenViewer_AlmBoxTableColor);
    WebSAGE.g_tbl.cellSpacing = "0px";
    WebSAGE.g_tbl.cellPadding = "0px";

    WebSAGE.g_tblhead.cellSpacing = "0px";
    WebSAGE.g_tblhead.cellPadding = "0px";
    $('#TBLHEAD').css('margin-left', '5px');
    $('#TBLHEAD').css('table-layout', 'fixed');
    $('#TBLHEAD').css('background-color', 'gainsboro');
    document.body.bgColor = 'gainsboro';
    $('#TBLHEAD').css('color', 'black');
    $('#TBLHEAD').css('font-family', TabularViewer_Font);

    setTimeout( WebSAGE.getGroup1List, 500 );
    
    // some com a toolbar e cabeçalho da caixa de alarmes 
    $('#TOOLBAR_ID').css('display', 'none');
    $('#divhead').css('display', 'none');

    // altera gridbox para ter scrollbar apenas no vertical, quando necessária
    $('#gridbox').css('overflow', '');
    $('#gridbox').css('overflow-x', 'hidden');
    $('#gridbox').css('overflow-y', 'auto');

    // verify if parent 'almiframe' contains this document
    if ( window.parent.document.getElementById('almiframe'))
    if ( window.parent.document.getElementById('almiframe').contentDocument === document )
      {
      // faz aparecer a caixa de alarmes 
      setTimeout ( "window.parent.document.getElementById('almbox').style.display = '';", 1000 );

      // aumenta o tamanho da caixa de alarmes quando passar o mouse, reduz quando tira
      Core.addEventListener( window.parent.document.getElementById('almiframe'), "mouseover",  
         function() { var box = window.parent.document.getElementById('almiframe');
                      var h = L.length * 13;
                      if ( h > 105 )
                         h = 105;
                      if ( box.height !== h )
                        box.height = h; 
                    } );
      Core.addEventListener( window.parent.document.getElementById('almiframe'), "mouseout",  
         function() { var box = window.parent.document.getElementById('almiframe');
                      var h = L.length * 13;
                      if ( h > 40 )
                         h = 40;                    
                      if ( box.height != h )
                        box.height = h; 
                    } );

      // monitora tamanho da janela, se ficar muito pequena some com a caixa de alarmes              
      window.parent.onresize = 
       function() 
          {
          if ( window.parent.innerWidth < 750 ) 
            {
            if ( window.parent.document.getElementById('almbox').style.display !== 'none' ) 
               window.parent.document.getElementById('almbox').style.display = 'none';
            }
          else
            {
            if ( window.parent.document.getElementById('almbox').style.display !== 'block' ) 
               window.parent.document.getElementById('almbox').style.display = 'block';
            }     
          };              
      }
        
    // desabilita o botão direito 
    document.oncontextmenu = function() { return false; };
    // torna elementos não selecionáveis
    $("html > head").append("<style> body { user-select:none; -webkit-user-select:none; } </style>");
    }
}
Core.start(WebSAGE);

</script>
</head>
<body style='margin:0px;'>
<table id="TOOLBAR_ID" style='font-family:tahoma;font-size:16px;' height='8%' width='100%' cellpadding='0' cellspacing='0'>
<tr height='40px'>
<td style='vertical-align:middle;height:36px;white-space:nowrap;'>
<img id='IMGTABULAR' alt='' src='' align='middle' width='32' height='32' onclick='WebSAGE.showInfo()' style='cursor:pointer;'/>
<img id='ANORM_ID' alt='' src='' align='middle' width='32' height='32' onclick='WebSAGE.showInfo()'  style='cursor:pointer;display:none;'/>
<img id='IMGSEPAR1' alt='' src='' align='middle' width='8' height='32' />
<img id="imgFontSizeUp" alt='' src='' align='middle' width="27" height="27" onclick='WebSAGE.fontSize(1)' style='cursor:pointer;' />
<img id="imgFontSizeDown" alt='' src='' align='middle' width="27" height="27" onclick='WebSAGE.fontSize(0)' style='cursor:pointer;' />
<img id='IMGSEPAR3' alt='' src='' align='middle' width='8' height='32' />
<div id='OPC_CMDANORM' style='display:none' >
<input id="cbMostraCmd" style='vertical-align:middle;' type="checkbox" onclick='WebSAGE.mostraCmd()' /><span id='SPCOMANDAVEIS'>?</span>&nbsp;
<input id="cbForaNormal" style='vertical-align:middle;' type="checkbox" onclick='WebSAGE.mostraForaNormal()' /><span id='SPANORMAIS'>?</span>&nbsp;
<img id='IMGSEPAR2' alt='' src='' align='middle' width='8' height='32' />
</div>
&nbsp;&nbsp;<small><span id="NUMREGS"> </span> <span id='ATUALIZACAO'> </span></small>

<div style="position: absolute; right: 0px; top: 0px; ">
<span id='VENTOINHA' style='font-family:courier;font-weight:bold;'>(!)</span>
</div>

</td>
</tr>
<tr>
<td style='vertical-align:middle;height:36px;white-space:nowrap;' >
<div id='OPC_FILTROS'> 
&nbsp;<span id='SPSUBEST'></span>:<select name="SELSE" id="SELSE" style='text-shadow: 1px 1px 1px #808080;' onfocus='this.onchange=WebSAGE.mudaSE;' size="1"></select>
<span id='SPMODULO'></span>:<select name="SELMOD" id="SELMOD" style='text-shadow: 1px 1px 1px #808080;' onchange='WebSAGE.mudaMod()' SIZE="1"></select>&nbsp;&nbsp;
&nbsp;<span id='SPFILTROID' style='display:none;'></span>
<input id="FiltroSE" style='display:none;text-shadow: 1px 1px 1px #808080;' onblur="WebSAGE.mudaFiltro();" onkeypress="if (event.witch==13 || event.keyCode==13) document.getElementById('SELSE').focus();" />
<input id="FiltroMod" style='display:none;text-shadow: 1px 1px 1px #808080;' onblur="WebSAGE.mudaFiltro();" onkeypress="if (event.witch==13 || event.keyCode==13) document.getElementById('SELSE').focus();" />
<input id="cbBloqAtu" onclick='WebSAGE.callServer()' type="checkbox" style="visibility:hidden;" />
<img id='SILENCIA_ID' alt='' src='' align='middle' width="32" height="32" onclick='document.getElementById("SILENCIA_ID").style.display="none";WebSAGE.doSilenceBeep();' style='display:none; cursor:pointer;' />
</div>
</td>
</tr>
</table>
<div id="divhead" style="width:100%;height:3%;min-height:18px;">
<table id="TBLHEAD" width="800px">
<tr><td></td></tr>
</table>
</div>
<div id="gridbox" style="width:100%; height:100%;">
<table id="TBLEVE" width="800px">
</table>
</div>
</body>
</html>
